import NPC from "../Entitys/NPC";
import {
  AnimationClip,
  Box3,
  BoxGeometry,
  BufferGeometry,
  Color,
  MathUtils,
  Mesh,
  MeshPhongMaterial,
  MeshStandardMaterial,
  SphereGeometry,
  Vector3,
} from "three";

import { OBB } from "three/examples/jsm/math/OBB";
import Util, { CloneGLTF, NewID } from "../Utils/Util";

export type EntityConfig = {
  modelUrl: string;
  scale: number;
  angle?: number;
  mater?: { [x: string]: any };
};

let gltfCache = {};

export default abstract class Entity {
  config: EntityConfig;
  model: Mesh<BufferGeometry, MeshStandardMaterial>;
  anims: AnimationClip[];

  size: Vector3;
  center: Vector3;
  id: string = NewID();

  originPosition: Vector3;
  matrixCoord: number[] = [];

  constructor(_config: EntityConfig) {
    this.config = _config;
  }

  async Builder() {
    await this.LoadModel();
    this.Behavior();
  }

  behaviors = ["UpdateOBB"];
  Behavior() {
    this.behaviors.forEach((m) => {
      render.addClockEvent(m + this.id, this[m].bind(this));
    });
  }

  boxSize = 0.2;
  async LoadModel() {
    switch (this.config.modelUrl) {
      case "assest/model/Player.glb":
        let body = new Mesh(
          new BoxGeometry(this.boxSize, this.boxSize, this.boxSize),
          new MeshPhongMaterial({ color: new Color("#55AAEE") })
        ) as any;

        let stickSize = 0.05;
        let stick = new Mesh(
          new BoxGeometry(stickSize, stickSize, stickSize * 5),
          new MeshPhongMaterial({ color: new Color("green") })
        );
        stick.rotation.z += Math.PI;
        stick.position.z += (stickSize * 5) / 2;
        body.add(stick);

        this.model = body;
        break;
      case "assest/model/Number-1.glb":
        this.model = new Mesh(
          new BoxGeometry(this.boxSize, this.boxSize, this.boxSize),
          new MeshPhongMaterial({ color: new Color("#B22222") })
        ) as any;
        break;
      case "bullet":
        let sphere = new SphereGeometry(0.05);
        this.model = new Mesh(sphere, new MeshPhongMaterial({ color: new Color("#DDAA44") })) as any;
        break;
    }
    this.model.castShadow = true;
    this.model.geometry.computeBoundingBox();
    this.model.geometry.userData.obb = new OBB().fromBox3(this.model.geometry.boundingBox as THREE.Box3);
    this.model.userData = {
      obb: new OBB(),
      entity: this,
      pos: {
        target: { y: this.boxSize / 2 },
        origin: { y: this.boxSize * 2 },
      },
    };

    this.model.position.y = this.model.userData.pos.origin.y;
  }

  UpdateOBB() {
    this.model.updateMatrix();
    this.model.updateMatrixWorld();
    (this.model.userData.obb as OBB).copy(this.model.geometry.userData.obb).applyMatrix4(this.model.matrixWorld);
  }

  async LoadModelBak() {
    let gltf = gltfCache[this.config.modelUrl];
    if (!gltf) {
      let fs = wx.getFileSystemManager();
      let content = fs.readFileSync(this.config.modelUrl);

      gltf = await Util.gltfLoader.parseAsync(content, this.config.modelUrl);

      this.model = gltf.scene;
      this.anims = gltf.animations;
      if (this.config.mater) this.SetMaterial();

      gltfCache[this.config.modelUrl] = CloneGLTF(gltf);
    } else {
      gltf = CloneGLTF(gltf);
      this.model = gltf.scene;
      this.anims = gltf.animations;
    }

    this.model.traverse((m: Mesh<BufferGeometry, MeshStandardMaterial>) => {
      if (m.isMesh) {
        m.castShadow = true;
      }
    });

    this.config.scale && this.model.scale.setScalar(this.config.scale);
    if (this.config.angle != undefined)
      this.model.rotation.y += this.config.angle || MathUtils.degToRad(Math.round(Math.random() * 360));

    let box = new Box3().setFromObject(this.model);
    this.center = new Vector3();
    this.size = new Vector3();
    box.getSize(this.size);
    box.getCenter(this.center);
  }
  SetMaterial() {
    this.model.traverse((m: Mesh<BufferGeometry, MeshStandardMaterial>) => {
      if (m.isMesh) {
        for (const k in this.config.mater) {
          let item = this.config.mater[k];
          m.material[k] = typeof item == "function" ? item(m.material) : item;
        }
        m.material.needsUpdate = true;
        m.castShadow = true;
      }
    });
  }

  Dispose() {
    this.model.geometry.dispose();
    this.model.material.dispose();
    this.model.removeFromParent();
    this.behaviors.forEach((m) => {
      render.removeClockEvent(m + this.id);
    });
  }
}
